package com.lt.demo.service;

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.lt.demo.domain.Image;
import com.lt.demo.domain.User;
import com.lt.demo.repository.ImageRepository;
import org.apache.http.HttpEntity;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.ContentType;
import org.apache.http.entity.StringEntity;
import org.apache.http.entity.mime.MultipartEntityBuilder;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.util.EntityUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

@Service
public class ImageService {

    private static final String UPLOAD_URL = "https://chaostong-animegan.hf.space/gradio_api/upload";
    private static final String PREDICT_URL = "https://chaostong-animegan.hf.space/gradio_api/call/predict";
    private static final String PREDICT_EVENT_URL_TEMPLATE = "https://chaostong-animegan.hf.space/gradio_api/call/predict/%s";
    private static final String TEMP_DIR = "/tmp/gradio/";
    private static final ObjectMapper objectMapper = new ObjectMapper();


    @Autowired
    private ImageRepository imageRepository;

    public Image storeImage(MultipartFile file, User user,String  style,Integer size,Boolean containOriginalColor) throws IOException {
        byte[] originalImageBytes = file.getBytes();
        // Call AI service to transform image
        byte[] transformedImageBytes = transformImage(file,style,size,containOriginalColor);
        //存储到数据库
        Image image = new Image();
        image.setOriginalFilename(file.getOriginalFilename());
        image.setOriginalImage(originalImageBytes);
        image.setTransformedImage(transformedImageBytes);
        image.setUserId(user.getId());
        return imageRepository.save(image);
    }

    public List<Image> getImagesByUser(User user) {
        List<Image> imageList = imageRepository.findByUserId(user.getId());
        if (imageList != null) {
            imageList = imageList.stream().filter(image -> image.getTransformedImage() != null && image.getTransformedImage().length > 10).collect(Collectors.toList());
        }
        return imageList;
    }

    private byte[] transformImage(MultipartFile file,String style,Integer size,Boolean containOriginalColor) {
        // Step 1: Upload the image
        byte[] imageBytes = new byte[0];
        try {
            String uploadedPath = uploadImage(file);

            // Step 2: Get the event ID
            String eventId = getEventId(uploadedPath,style,size,containOriginalColor);

            Thread.sleep(3000);

            // Step 3: Retrieve the transformed image
            imageBytes = retrieveTransformedImage(eventId);
        } catch (Exception e) {
             e.printStackTrace();
            throw new RuntimeException("transformImage", e);
        }

        return imageBytes;
    }

    /**
     * Step 1: Upload the image to the algorithm server.
     *
     * @param file MultipartFile to upload
     * @return The path returned by the server
     * @throws IOException If an I/O error occurs
     */
    private String uploadImage(MultipartFile file) throws IOException {
        // Prepare HTTP client with 5 seconds timeout
        RequestConfig requestConfig = RequestConfig.custom()
                .setConnectTimeout(5000) // Connection timeout
                .setConnectionRequestTimeout(5000) // Request timeout
                .setSocketTimeout(5000) // Socket timeout
                .build();

        try (CloseableHttpClient httpClient = HttpClients.custom()
                .setDefaultRequestConfig(requestConfig)
                .build()) {

            HttpPost uploadPost = new HttpPost(UPLOAD_URL);

            // Create multipart entity
            MultipartEntityBuilder builder = MultipartEntityBuilder.create();
            builder.addBinaryBody("files", convertMultipartFileToFile(file), ContentType.DEFAULT_BINARY, file.getOriginalFilename());

            HttpEntity multipart = builder.build();
            uploadPost.setEntity(multipart);

            // Execute the request
            try (CloseableHttpResponse response = httpClient.execute(uploadPost)) {
                int statusCode = response.getStatusLine().getStatusCode();
                if (statusCode != 200 && statusCode != 201) {
                    throw new IOException("Failed to upload image. HTTP Status Code: " + statusCode);
                }

                String responseString = EntityUtils.toString(response.getEntity());
                System.out.println("uploadImage  responseString is "+responseString);
                // Expected response format: ["/tmp/gradio/...."]
                JsonNode jsonNode = objectMapper.readTree(responseString);
                if (jsonNode.isArray() && jsonNode.size() > 0) {
                    return jsonNode.get(0).asText();
                } else {
                    throw new IOException("Unexpected upload response: " + responseString);
                }
            }
        }
    }

    /**
     * Step 2: Get the event ID by sending the uploaded path.
     *
     * @param uploadedPath Path returned from upload step
     * @return event_id string
     * @throws IOException If an I/O error occurs
     */
    private String getEventId(String uploadedPath,String  style,Integer size,Boolean containOriginalColor) throws IOException {
        // Prepare HTTP client with 5 seconds timeout
        RequestConfig requestConfig = RequestConfig.custom()
                .setConnectTimeout(5000) // Connection timeout
                .setConnectionRequestTimeout(5000) // Request timeout
                .setSocketTimeout(5000) // Socket timeout
                .build();

        try (CloseableHttpClient httpClient = HttpClients.custom()
                .setDefaultRequestConfig(requestConfig)
                .build()) {

            HttpPost predictPost = new HttpPost(PREDICT_URL);
            predictPost.setHeader("Content-Type", "application/json");

            // Prepare JSON payload
            List<Object> payload = new ArrayList<>();
            Map<String, String> pathMap = new HashMap<>();
            pathMap.put("path", uploadedPath);
            payload.add(pathMap);
            payload.add(style);
            payload.add(size);
            payload.add(containOriginalColor);
            Map<String, Object> requestMap = new HashMap<>();
            requestMap.put("data", payload);
            String jsonPayload = objectMapper.writeValueAsString(requestMap
            );

            StringEntity entity = new StringEntity(jsonPayload, ContentType.APPLICATION_JSON);
            predictPost.setEntity(entity);

            // Execute the request
            try (CloseableHttpResponse response = httpClient.execute(predictPost)) {
                int statusCode = response.getStatusLine().getStatusCode();
                if (statusCode != 200 && statusCode != 201) {
                    throw new IOException("Failed to get event ID. HTTP Status Code: " + statusCode);
                }
                String responseString = EntityUtils.toString(response.getEntity());
                System.out.println("get eventId responseString is "+responseString);
                JsonNode jsonNode = objectMapper.readTree(responseString);
                if (jsonNode.has("event_id")) {
                    return jsonNode.get("event_id").asText();
                } else {
                    throw new IOException("Unexpected event ID response: " + responseString);
                }
            }
        }
    }

    /**
     * Step 3: Retrieve the transformed image using the event ID.
     *
     * @param eventId The event ID obtained from the previous step
     * @return Byte array of the transformed image
     * @throws Exception If an error occurs during retrieval
     */
    private byte[] retrieveTransformedImage(String eventId) throws Exception {
        String predictEventUrl = String.format(PREDICT_EVENT_URL_TEMPLATE, eventId);

        // Prepare HTTP client with 20 seconds timeout
        RequestConfig requestConfig = RequestConfig.custom()
                .setConnectTimeout(20000) // Connection timeout
                .setConnectionRequestTimeout(20000) // Request timeout
                .setSocketTimeout(20000) // Socket timeout
                .build();

        try (CloseableHttpClient httpClient = HttpClients.custom()
                .setDefaultRequestConfig(requestConfig)
                .build()) {

            HttpGet getRequest = new HttpGet(predictEventUrl);

            // Execute the request
            try (CloseableHttpResponse response = httpClient.execute(getRequest)) {
                int statusCode = response.getStatusLine().getStatusCode();
                if (statusCode != 200 && statusCode != 201) {
                    throw new IOException("Failed to retrieve transformed image. HTTP Status Code: " + statusCode);
                }
                // 读取响应内容
                InputStream contentStream = response.getEntity().getContent();
                BufferedReader reader = new BufferedReader(new InputStreamReader(contentStream, StandardCharsets.UTF_8));
                String line;
                String dataJson = null;

                while ((line = reader.readLine()) != null) {
                    if (line.startsWith("data:")) {
                        // 提取 "data:" 后面的 JSON
                        dataJson = line.substring("data:".length()).trim();
                        break; // 只需要最后一个 data: 行
                    }
                }

                if (dataJson == null) {
                    throw new IOException("No data found in the response.");
                }
                System.out.println("convert image,json is "+dataJson);
                // 解析 dataJson
                JsonNode dataArray;
                try {
                    dataArray = objectMapper.readTree(dataJson);
                } catch (Exception e) {
                    throw new IOException("Failed to parse data JSON: " + dataJson, e);
                }

                if (!dataArray.isArray() || dataArray.size() < 2) {
                    throw new IOException("Data array does not contain enough elements: " + dataJson);
                }

                JsonNode secondElement = dataArray.get(1);
                if (!secondElement.has("url")) {
                    throw new IOException("Missing 'url' in second element of data: " + dataJson);
                }

                String imageUrl = "https://chaostong-animegan.hf.space/gradio_api/file="+secondElement.get("path").asText();
                System.out.println("convert image url is "+imageUrl);
                return downloadImageBytes(imageUrl);
            }
        }
    }

    /**
     * Downloads the image bytes from the given URL.
     *
     * @param imageUrl The URL of the image to download
     * @return Byte array of the image
     * @throws IOException If an I/O error occurs
     */
    private byte[] downloadImageBytes(String imageUrl) throws IOException {
        // Reuse the 20 seconds timeout configuration
        RequestConfig requestConfig = RequestConfig.custom()
                .setConnectTimeout(20000) // Connection timeout
                .setConnectionRequestTimeout(20000) // Request timeout
                .setSocketTimeout(20000) // Socket timeout
                .build();

        try (CloseableHttpClient httpClient = HttpClients.custom()
                .setDefaultRequestConfig(requestConfig)
                .build()) {

            HttpGet getImage = new HttpGet(imageUrl);

            // Execute the request
            try (CloseableHttpResponse response = httpClient.execute(getImage)) {
                int statusCode = response.getStatusLine().getStatusCode();
                if (statusCode != 200) {
                    throw new IOException("Failed to download image. HTTP Status Code: " + statusCode);
                }

                HttpEntity entity = response.getEntity();
                if (entity != null) {
                    return EntityUtils.toByteArray(entity);
                } else {
                    throw new IOException("Empty response while downloading image.");
                }
            }
        }
    }

    /**
     * Converts MultipartFile to File.
     *
     * @param multipartFile The MultipartFile to convert
     * @return The converted File
     * @throws IOException If an I/O error occurs
     */
    private File convertMultipartFileToFile(MultipartFile multipartFile) throws IOException {
        Path tempDirPath = Paths.get(TEMP_DIR);
        if (!Files.exists(tempDirPath)) {
            Files.createDirectories(tempDirPath);
        }

        File tempFile = File.createTempFile("upload-", "-" + multipartFile.getOriginalFilename(), tempDirPath.toFile());
        try (FileOutputStream fos = new FileOutputStream(tempFile)) {
            fos.write(multipartFile.getBytes());
        }
        return tempFile;
    }

    public Image storeFakeImage(MultipartFile file, User user, String style, Integer size, Boolean containOriginalColor) throws IOException {
        byte[] originalImageBytes = file.getBytes();
        //存储到数据库
        Image image = new Image();
        image.setOriginalFilename(file.getOriginalFilename());
        image.setOriginalImage(originalImageBytes);
        image.setTransformedImage(originalImageBytes);
        image.setUserId(user.getId());
        return imageRepository.save(image);
    }
}